%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import cvxopt as opt
from cvxopt import blas, solvers
import pandas as pd
np.random.seed(123)
# Turn off progress printing
solvers.options['show_progress'] = False
import plotly
import cufflinks
# (*) To communicate with Plotly's server, sign in with credentials file
import chart_studio.plotly as py
# (*) Useful Python/Plotly tools
import plotly.tools as tls
# (*) Graph objects to piece together plots
from plotly.graph_objs import *
stocks_df = pd.read_csv('stock.csv').drop(columns = "Unnamed: 0")
stocks_df
| Date | TSLA | WL-MART | AMZN | MSFOT | VISA | AAPL | GOOGL | NTFLX | JP-MORGN | NVDA | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 07-08-2017 | 71.470001 | 80.570000 | 49.532501 | 72.800003 | 100.919998 | 39.264999 | 46.452999 | 181.000000 | 93.889999 | 42.097500 |
| 1 | 08-08-2017 | 71.505997 | 81.169998 | 49.717499 | 72.089996 | 101.500000 | 39.650002 | 46.354500 | 181.369995 | 93.949997 | 43.472500 |
| 2 | 09-08-2017 | 72.199997 | 81.110001 | 49.130001 | 72.250000 | 100.760002 | 39.814999 | 46.030499 | 171.429993 | 92.980003 | 42.107498 |
| 3 | 10-08-2017 | 72.320000 | 81.070000 | 48.814999 | 71.900002 | 100.540001 | 39.974998 | 45.877499 | 174.029999 | 92.900002 | 43.040001 |
| 4 | 11-08-2017 | 71.393997 | 80.730003 | 48.000000 | 71.610001 | 99.550003 | 39.150002 | 45.398499 | 169.860001 | 92.129997 | 39.285000 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1253 | 29-07-2022 | 842.099976 | 128.320007 | 134.899994 | 277.700012 | 212.000000 | 161.240005 | 113.400002 | 223.289993 | 115.589996 | 178.130005 |
| 1254 | 01-08-2022 | 903.830017 | 131.059998 | 134.960007 | 277.820007 | 208.449997 | 161.009995 | 115.529999 | 223.100006 | 114.500000 | 181.820007 |
| 1255 | 02-08-2022 | 882.010010 | 133.149994 | 134.720001 | 276.000000 | 207.800003 | 160.100006 | 114.430000 | 222.759995 | 113.919998 | 181.220001 |
| 1256 | 03-08-2022 | 915.000000 | 132.160004 | 136.210007 | 276.760010 | 207.990005 | 160.839996 | 116.339996 | 224.789993 | 113.449997 | 181.839996 |
| 1257 | 04-08-2022 | 933.000000 | 130.669998 | 140.580002 | 281.799988 | 210.529999 | 166.009995 | 118.300003 | 227.679993 | 112.849998 | 188.490005 |
1258 rows × 11 columns
np.random.seed(1245231)
# Create random weights for the stocks and normalize them
weights = np.array(np.random.random(10))
# Ensure that the sum of all weights are = 1
weights = weights / np.sum(weights)
print(weights)
[0.10238429 0.16104134 0.07857652 0.14602368 0.1111931 0.14945584 0.02853777 0.07222408 0.10414996 0.04641342]
from usfl_func import *
df_portfolio = portfolio_allocation(df = stocks_df, weights = weights)
df_portfolio
| Date | TSLA | WL-MART | AMZN | MSFOT | VISA | AAPL | GOOGL | NTFLX | JP-MORGN | NVDA | portfolio daily worth in $ | portfolio daily % return | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 07-08-2017 | 5119.214677 | 8052.066909 | 3928.825849 | 7301.184136 | 5559.655017 | 7472.791869 | 1426.888335 | 3611.204103 | 5207.498148 | 2320.670958 | 50000.000000 | 0.000000 |
| 1 | 08-08-2017 | 5121.792979 | 8112.029972 | 3943.499546 | 7229.976833 | 5591.607168 | 7546.064437 | 1423.862759 | 3618.586023 | 5210.825866 | 2396.469344 | 50194.714927 | 0.389430 |
| 2 | 09-08-2017 | 5171.502437 | 8106.033946 | 3896.900298 | 7246.023792 | 5550.840881 | 7577.466150 | 1413.910479 | 3420.269029 | 5157.026292 | 2321.222109 | 49861.195411 | -0.664451 |
| 3 | 10-08-2017 | 5180.097947 | 8102.036295 | 3871.914925 | 7210.922147 | 5538.721086 | 7607.916660 | 1409.210806 | 3472.142798 | 5152.589131 | 2372.627361 | 49918.179156 | 0.114285 |
| 4 | 11-08-2017 | 5113.770703 | 8068.057412 | 3807.270720 | 7181.837661 | 5484.182368 | 7450.906000 | 1394.497450 | 3388.945484 | 5109.881711 | 2165.628804 | 49164.978313 | -1.508871 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1253 | 29-07-2022 | 60317.482808 | 12824.144000 | 10700.016611 | 27850.808221 | 11679.021868 | 30686.693724 | 3483.287268 | 4454.948834 | 6411.062909 | 9819.612312 | 178227.078555 | 1.713870 |
| 1254 | 01-08-2022 | 64739.048884 | 13097.975337 | 10704.776730 | 27862.842638 | 11483.453176 | 30642.918940 | 3548.713999 | 4451.158327 | 6350.607565 | 10023.027728 | 182904.523325 | 2.624430 |
| 1255 | 02-08-2022 | 63176.137194 | 13306.846972 | 10685.739901 | 27680.312340 | 11447.645185 | 30469.732678 | 3514.925530 | 4444.374630 | 6318.438438 | 9989.951738 | 181034.104605 | -1.022620 |
| 1256 | 03-08-2022 | 65539.126401 | 13207.908587 | 10803.924405 | 27756.534493 | 11458.112343 | 30610.565262 | 3573.594530 | 4484.875940 | 6292.370386 | 10024.129644 | 183751.141991 | 1.500843 |
| 1257 | 04-08-2022 | 66828.420691 | 13058.999216 | 11150.544280 | 28261.998860 | 11598.039916 | 31594.503310 | 3633.799709 | 4542.535497 | 6259.092149 | 10390.718699 | 187318.652327 | 1.941490 |
1258 rows × 13 columns
metrics = portfolio_statistical_analysis(stocks_df, weights)
print(" - Portfolio Cummulative Return = {:.02f}%".format(metrics[0]))
print(' - Portfolio Expected return per year = {:.02f}%'.format(metrics[1] * 100))
print(' - Volatility (standard deviation) of the portfolio = {:.02f}%'.format(metrics[2] * 100))
print(' - Sharpe ratio of the portfolio = {}'.format(metrics[3]))
- Portfolio Cummulative Return = 274.64% - Portfolio Expected return per year = 27.92% - Volatility (standard deviation) of the portfolio = 24.76% - Sharpe ratio of the portfolio = 1.127892923864166
# Create random weights and analayze the portfolio to find the optimal weight allocation
number_of_trials = 2000
# Placeholder to store the weights
possible_weights_runs = np.zeros((number_of_trials, 10))
# Placeholder to store the sharpe ratios
sharpe_ratio_runs = np.zeros(number_of_trials)
# Placeholder to store the returns
expected_portfolio_returns_runs = np.zeros(number_of_trials)
# Placeholder to store the volatility
volatility_runs = np.zeros(number_of_trials)
# Placeholder to store the cummulative returns
cummulative_returns_runs = np.zeros(number_of_trials)
np.random.seed(0)
import time
for i in range(number_of_trials):
# Random Weights
t = 1000 * time.time()
np.random.seed(int(t) % 2**32)
weights = np.array(np.random.random(10))
weights = weights / np.sum(weights)
# Store the weights
possible_weights_runs[i,:] = weights
# Store the sharpe ratio, return and volatility
cummulative_returns_runs[i], expected_portfolio_returns_runs[i], volatility_runs[i], sharpe_ratio_runs[i] = portfolio_statistical_analysis(stocks_df, weights)
sharpe_ratio_runs.max()
1.2449486308697835
possible_weights_runs[sharpe_ratio_runs.argmax(),:]
array([0.18464303, 0.14375508, 0.04119454, 0.1889962 , 0.01743844,
0.20889257, 0.06154497, 0.02191524, 0.00496874, 0.1266512 ])
metrics = portfolio_statistical_analysis(stocks_df, possible_weights_runs[sharpe_ratio_runs.argmax(), :])
print(" - Portfolio Cummulative Return = {:.02f}%".format(metrics[0]))
print(' - Portfolio Expected return per year = {:.02f}%'.format(metrics[1] * 100))
print(' - Volatility (standard deviation) of the portfolio = {:.02f}%'.format(metrics[2] * 100))
print(' - Sharpe ratio of the portfolio = {}'.format(metrics[3]))
- Portfolio Cummulative Return = 416.90% - Portfolio Expected return per year = 37.03% - Volatility (standard deviation) of the portfolio = 29.74% - Sharpe ratio of the portfolio = 1.2449486308697835
# Create a DataFrame with all volatilties, portfolio returns, and sharpe ratios
df_optimize = pd.DataFrame({'Volatility': volatility_runs.tolist(), 'Portfolio_Return': expected_portfolio_returns_runs.tolist(), 'Sharpe_Ratio': sharpe_ratio_runs.tolist() })
# Plot all the obtained volatility vs. returns
# Highlight the volatility and return corresponding to highest sharpe ratio
fig = px.scatter(df_optimize, x = 'Volatility', y = 'Portfolio_Return', color = 'Sharpe_Ratio', size = 'Sharpe_Ratio', hover_data = ['Sharpe_Ratio'] )
# Let's add this line to highlight the point with the highest Sharpe Ratio
fig.add_trace(go.Scatter(x = [metrics[2]], y = [metrics[1]], mode = 'markers', name = 'Optimal Point', marker = dict(size=[50], color = 'black')))
fig.update_layout(coloraxis_colorbar = dict(
title = "Sharpe Ratio",
thicknessmode = "pixels", thickness = 25,
lenmode = "pixels",
yanchor = "middle", y = 0.4,
dtick = 5
))
fig.show()
from scipy.optimize import minimize
# Since optimization works as a minimizing function, we take negative of sharpe ratio and then try minimize it.
# Define a function that calculates the negative Sharpe ratio
def calculate_negative_sharpe_func(weights, df = stocks_df):
cum_return, exp_portfolio_return, volatility, sharpe_ratio = portfolio_statistical_analysis(stocks_df, weights)
return sharpe_ratio * -1
# Function to define optimization constraints (make sure sum of all weights add to 1)
def optimization_constraints_func(weights, df = stocks_df,):
return np.sum(weights) - 1
# Function to obtain the "volatility" for a given set of portfolio weights
def calculate_volatility_func(weights, df = stocks_df):
cum_return, exp_portfolio_return, volatility, sharpe_ratio = portfolio_statistical_analysis(stocks_df, weights)
return volatility
# Function to get the return for a particular weight
def calculate_return_func(weights, df = stocks_df):
cum_return, exp_portfolio_return, volatility, sharpe_ratio = portfolio_statistical_analysis(stocks_df, weights)
return exp_portfolio_return
# Constraints to pass to the optimizer
optimization_constraint = ({'type':'eq','fun': optimization_constraints_func})
# Lower and upper bounds for weights
bounds = ((0, 1), (0, 1), (0, 1), (0, 1),(0, 1), (0, 1), (0, 1), (0, 1),(0,1), (0, 1))
# Initial weight assumption
t = 1000 * time.time()
np.random.seed(int(t) % 2**32)
weights = np.array(np.random.random(10))
initialization = weights / np.sum(weights)
sum(initialization)
1.0
# Optimization Function
# Optimization Algorithms is SLSQP stands for Sequential Least Squares Programming (SLSQP)
optimization_results = minimize(calculate_negative_sharpe_func, initialization, method = 'SLSQP', bounds = bounds, constraints = optimization_constraint)
optimized_weights = optimization_results.x
optimized_weights
array([2.19062537e-01, 1.43329371e-01, 5.39390581e-17, 4.45791641e-01,
3.69848466e-17, 1.91816451e-01, 4.64038530e-17, 3.49113100e-17,
3.02051949e-18, 1.15603057e-17])
# Analyze the portfolio based on the obtained weights
cummulative_return_SLSQP, portfolio_return_value_SLSQP, vol_value_SLSQP, sharpe_ratio_SLSQP = portfolio_statistical_analysis(stocks_df, optimized_weights)
print('Best Portfolio metrics based on SLSQP optimizer:')
print(' - Optimal Cummulative return of the portfolio = {}%'.format(cummulative_return_SLSQP))
print(' - Portfolio Expected return per year = {:.02f}%'.format(portfolio_return_value_SLSQP * 100))
print(' - Volatility of the portfolio = {:.02f}%'.format(vol_value_SLSQP * 100))
print(' - Sharpe ratio of the portfolio = {}'.format(sharpe_ratio_SLSQP))
Best Portfolio metrics based on SLSQP optimizer: - Optimal Cummulative return of the portfolio = 462.87840798510445% - Portfolio Expected return per year = 38.20% - Volatility of the portfolio = 28.82% - Sharpe ratio of the portfolio = 1.3255064869799182